#-*- coding: utf-8 -*-
import os
import yaml
import shutil
import logging
import traceback

from selenose.excepts import GridError
from selenose.tools import Starter, get_last_version, get_available_port, kill, java, create
from selenose.consts import LOCALHOST, LOGGER, DEFINE_PORT_ON_STARTUP, HUB_START_DELAY, LIB_GRID_HUB, LIB_GRID_RC, LIB_SERVER

# Get a logger
logger =  logging.getLogger('%s.%s' % (LOGGER, __name__))

class Node(object):
    
    def __init__(self, host, port, exe, libs, work):
        self.exe = exe
        self.host = host
        self.port = port
        self.libs = libs
        self.process = None
        self.work = work
        self.log = None

    def start(self):
        raise NotImplementedError
    
    def stop(self):
        if self.process is not None:
            logger.info('stop %s' % self)
            kill(self.process)
            self.process = None
        if self.log is not None:
            self.log.close()
    
    def clean(self):
        if self.log is not None:
            if os.path.isfile(self.log.name):
                os.remove(self.log.name)
            self.log = None

class Hub(Node):
    
    def __init__(self, host, port, exe, libs, work, path):
        Node.__init__(self, host, port, exe, libs, work)
        self.path = path
        
    def get_class_path(self):
        return '%s%s%s' % (os.path.dirname(self.path),
                           os.pathsep,
                           get_last_version(self.libs, 'selenium-grid-hub-standalone-*.jar', default=LIB_GRID_HUB), )

    def start(self):
        logger.info('start %s' % self)
        self.log = open(os.path.join(self.work, 'hub.log'), 'a')
        starter = Starter(self.log)
        self.process = java(self.exe, self.get_class_path(), 'com.thoughtworks.selenium.grid.hub.HubServer', self.log)
        starter.wait('Started SocketConnector', HUB_START_DELAY)

    def geturl(self):
        return 'http://%s:%s/' % (self.host, self.port)

    def __str__(self):
        return 'hub(%s:%d)' % (self.host, self.port)

class Rc(Node):
    
    def __init__(self, host, port, exe, libs, work, hub, environ, browser):
        Node.__init__(self, host, port, exe, libs, work)
        self.hub = hub
        self.environ = environ
        self.browser = browser

    def get_class_path(self):
        return '%s%s%s' % (get_last_version(self.libs, 'selenium-server-standalone*.jar', default=LIB_SERVER),
                           os.pathsep,
                           get_last_version(self.libs, 'selenium-grid-remote-control-standalone-*.jar', default=LIB_GRID_RC), )

    def start(self):
        if self.port == DEFINE_PORT_ON_STARTUP:
            self.port = get_available_port()
        logger.info('start %s' % self)
        self.log = open(os.path.join(self.work, 'rc_%d.log' % self.port), 'a')
        self.process = java(self.exe, self.get_class_path(),
                            'com.thoughtworks.selenium.grid.remotecontrol.SelfRegisteringRemoteControlLauncher',
                            self.log,
                            '-port', '%d' % self.port,
                            '-host', self.host,
                            '-hubUrl', self.hub,
                            '-env', self.environ, )

    def __str__(self):
        return 'rc(%s:%d, %s, %s)' % (self.host, self.port, self.environ, self.browser)

class Configuration(object):
    
    def __init__(self, path):
        self.path = os.path.abspath(os.path.normpath(path))
        if not os.path.isfile(self.path):
            raise GridError("configuration file doesn't exist: %s" % self.path)
        bn = os.path.basename(self.path)
        if bn != 'grid_configuration.yml':
            raise GridError('invalid configuration file: %s (grid_configuration.yml expected)' % bn)
        fd = open(self.path, 'r')
        content = fd.read()
        fd.close()
        self.data = yaml.load(content)
    
    def get(self, *args):
        value = self.data
        for key in args:
            value = value.get(key)
            if value is None: return None
        return value
    
    def get_hub_port(self):
        port = self.get('hub', 'port')
        if port is None: return 44444
        else: return int(port)
    
    def get_hub_host(self):
        return LOCALHOST
    
    def get_environs(self):
        environs = self.get('hub', 'environments')
        if environs is None: return []
        else: return [ (entry.get('name'), entry.get('browser')) for entry in environs ]

class Grid(object):
    '''Take care of the Grid'''
    
    def __init__(self, config, exe, libs, work):
        self.config = Configuration(config)
        self.libs = [ os.path.abspath(os.path.normpath(lib)) for lib in libs ]
        for lib in self.libs:
            if not os.path.isdir(lib):
                raise GridError("library folder doesn't exist: %s" % lib)
        self.work = os.path.abspath(os.path.normpath(work))
        self.delete = create(self.work)
        self.exe = os.path.normpath(exe)
        self.hub = Hub(self.config.get_hub_host(), self.config.get_hub_port(), self.exe, self.libs, self.work, self.config.path)
        self.rcs = []
        for environ, browser in self.config.get_environs():
            rc = Rc(self.config.get_hub_host(), DEFINE_PORT_ON_STARTUP, self.exe, self.libs, self.work, self.hub.geturl(), environ, browser)
            self.rcs.append(rc)
            
    def start(self):
        try:
            for node in self.get_all(first=True):
                node.start()
        except BaseException:
            logger.error(traceback.format_exc())
            logger.error('failed to start %s' % node)
            self.stop()
            raise GridError('failed to start all nodes')

    def get_all(self, first=True):
        if first: yield self.hub
        for rc in self.rcs:
            yield rc
        if not first: yield self.hub

    def stop(self):
        error = False
        for node in self.get_all(first=False):
            try:
                node.stop()
            except BaseException:
                logger.error(traceback.format_exc())
                logger.error('failed to stop %s' % node)
                error = True
        if error: raise GridError('failed to stop all nodes')

    def clean(self):
        if os.path.isdir(self.work):
            if self.delete:
                shutil.rmtree(self.work)
            else:
                error = False
                for node in self.get_all():
                    try:
                        node.clean()
                    except BaseException:
                        logger.error(traceback.format_exc())
                        logger.error('failed to clean %s' % node)
                        error = True
                if error: raise GridError('failed to clean all grid nodes')
    